<?php
/**
 * Here the class '\UF\Config\Value' is defined.
 *
 * @category   UniKap-Framework
 * @package    Config
 * @subpackage Core
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-03-29 16:17
 * @version    0.1
 */

namespace UF\Config
{

    /**
     * Diese Klasse stellt ein Objekt dar, das alle nötigen Infos zu einem
     * Konfigurationswert aufnehmen kann.
     *
     * Ein Wert kann als in 2 unterschiedlichen Wert-Grundtypen festgelegt
     * werden.
     *
     * - Variante 1 nutzt eine strikte Typisierung und kann damit immer nur
     *   Werte eines vordefinierten Types enthalten.
     * - Variante 2 erlaubt die Nutzung wechselnder Typen. Der Typ ist hier also
     *   immer abhängig von aktuell gesetzten Wert.
     *
     * @property      mixed  $value The current config value.
     * @property-read string $name  The config value name.
     * @property-read bool   $isstrict Use a strict type? (No type change!)
     * @property-read string $type Gibt den aktuell gesetzten Typ des Configwertes zurück.
     * @since         v0.1
     */
    class Value implements \UF\IStringConverter, \Serializable
    {

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - -">

        private $_value;
        private $_type;
        private $_isStrict;
        private $_name;

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - >   C O N S T R U C T O R   - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Inits a new instance.
         *
         * @param  string $name The config value name.
         * @param  mixed  $value The config value.
         * @param  bool   $isStrict Use a strict type? (No type change!)
         * @param  string $typeName If $isStrict is TRUE her you have to define the fully qualified name of the type
         *         of the config value. Its important! If you dont define a type here, so you can not use NULL values!
         * @throws \UF\ArgumentException If no name is defined or if $value is NULL + $isStrict=true + no $typeName
         * @since  v0.1
         */
        public function __construct( $name = '', $value = null, $isStrict = false, $typeName = null )
        {
            if ( $isStrict && \is_null( $value ) && empty( $typeName ) ) {
                throw new \UF\ArgumentException(
                'value', $value, \sprintf(
                        'Could not use NULL in case of a strictly typed value requirement of config value "%s" ' .
                        'and not defined default type.', $name ) );
            }
            $this->_name     = $name;
            $this->_value    = $value;
            $this->_isStrict = $isStrict;
            $this->_type     = empty( $typeName ) ? \UF\TypeTool::GetTypeName( $value ) : $typeName;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   G E T T E R S   - - - - - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * overrides the magic method for using dynamic properties for read access.
         *
         * @param  string $name The name of the dynamic field.
         * @return mixed The value of the dynamic field.
         * @throws \UF\ArgumentException If $name points to a undefined field.
         * @since  v0.1
         */
        public function __get( $name )
        {
            switch ( \strtolower( $name ) )
            {
                case 'name': return $this->_name;
                case 'value': return $this->_value;
                case 'type': return $this->_type;
                case 'isstrict': return $this->_isStrict;
                default: throw new \UF\ArgumentException(
                    'name', $name, \sprintf( 'Illegal access of a not existing dynamic property of config value "%s"!', $this->_name ) );
            }
        }

        /**
         * Return the current config value name.
         *
         * @return string
         * @since  v0.1
         */
        public final function getName()
        {
            return $this->_name;
        }

        /**
         * Returns the current config value.
         *
         * @return mixed
         * @since  v0.1
         */
        public final function getValue()
        {
            return $this->_value;
        }

        /**
         * Returns the current config value type name. (e.g. 'string', 'int', etc.)
         *
         * @return string
         * @since  v0.1
         */
        public final function getType()
        {
            return $this->_type;
        }

        /**
         * Returns, if the current config value uses a strict type. If so, it only accepts value changes to a
         * values with the same type, or to NULL value.
         *
         * @return bool
         * @since  v0.1
         */
        public final function isStrictlyTyped()
        {
            return $this->_isStrict;
        }

        #</editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   S E T T E R S   - - - - - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Overrides the magic method for using dynamic fields in write mode.
         *
         * @param  string $name The name of the field to change value for (only 'value' is supported!)
         * @param  mixed  $value The new configuration value.
         * @throws \UF\Exception If 'name', 'type' or 'isstrict' should be changed.
         * @throws \UF\ArgumentException  If $name point to a unknown field
         * @since  v0.1
         */
        public function __set( $name, $value )
        {
            switch ( \strtolower( $name ) )
            {
                case 'value': setValue( $value );
                    break;
                case 'name': case 'type': case 'isstrict':
                    throw new \UF\Exception(
                    \sprintf(
                            '(Name, Type, IsStrict) property of configvalue "%s" is readonly! ' .
                            'Directly changing this value fails!', $this->_name ) );
                default: throw new \UF\ArgumentException(
                    'name', $name, \sprintf(
                            'Illegal access of a not existing dynamic property of config value "%s"!', $this->_name ) );
            }
        }

        /**
         * Sets a new value.
         *
         * @param  mixed $value The new value.
         * @throws \UF\ArgumentException If value is not accepted.
         * @since  v0.1
         */
        public final function setValue( $value )
        {
            if ( \is_null( $value ) ) {
                $this->_value = null;
                return;
            }
            $type = \UF\TypeTool::GetTypeName( $value );
            if ( !$this->_isStrict ) {
                $this->_type  = $type;
                $this->_value = $value;
                return;
            }
            if ( $this->_type != $type ) {
                throw new \UF\ArgumentException(
                'type', $type, \sprintf( 'Could not change type of strictly typed config value "%s"!', $this->_name ) );
            }
            $this->_value = $value;
        }

        #</editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Overrides the magic method.
         *
         * @return string
         * @since  v0.1
         */
        public function __toString()
        {
            if ( \is_null( $this->_value ) ) return '<NULL>';
            $t = new \UF\Type( $this->_value );
            return $t->getStringValue( '<NULL>' );
        }

        /**
         * Reinits the Konfig value from a string.
         *
         * @param  string $str Zeichenkette die die Daten einer
         *                Instanz des implementierenden Objekts entspricht.
         * @return bool
         * @since  v0.1
         */
        public function fromString( $str )
        {
            if ( \UF\InterfaceHelper::IsFromStringConverter( $this->_value ) ) {
                $this->_value->fromString( $str );
                return true;
            }
            $this->_value = \UF\TypeTool::StrToType( $str, $this->_type );
            return true;
        }

        /**
         * Returns a array with all instance data. Arraykeys are 'name', 'type', 'strict' and 'value'.
         *
         * @return array
         */
        public function toArray()
        {
            return array (
                'name'   => $this->_name,
                'type'   => $this->_type,
                'strict' => $this->_isStrict,
                'value'  => $this->_value
            );
        }

        /**
         * Implements \Serializable
         *
         * @return string
         */
        public function serialize()
        {
            return \serialize( $this->toArray() );
        }

        /**
         * Implements \Serializable
         *
         * @param string $serialized
         * @throws \UF\ArgumentException
         */
        public function unserialize( $serialized )
        {
            if ( empty( $serialized ) ) { throw new \UF\ArgumentException(
                'serialized', $serialized,
                'Unable to deserialize empty data to a \\UF\\Config\\Value instance!' ); }
            if ( !\is_string( $serialized ) ) { throw new \UF\ArgumentException(
                'serialized', $serialized,
                'Unable to deserialize data not defined by a string to a \\UF\\Config\\Value instance!' ); }
            try { $array = \unserialize( $serialized ); }
            catch ( \Exception $ex ) {
                $ex = null; throw new \UF\ArgumentException(
                    'serialized', $serialized,
                    'Unable to deserialize data if data do not use not a valid php serialization format!' ); }
            if ( !isset( $array ) || !\is_array( $array ) ) { throw new \UF\ArgumentException(
                'serialized', $serialized,
                'Unable to deserialize config value data if data do not match required format!' ); }
            if ( !$this->fromArray( $array ) ) { throw new \UF\ArgumentException(
                'serialized', $serialized,
                'Unable to deserialize config value data if data do not match required format!' ); }
        }

        # </editor-fold>

    }

}

